/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.rest.meta.service;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

import org.springframework.beans.factory.annotation.Autowired;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.meta.context.GetDataModelContext;
import org.unidata.mdm.meta.context.GetDataModelContext.GetDataModelContextBuilder;
import org.unidata.mdm.meta.context.UpsertDataModelContext;
import org.unidata.mdm.meta.context.UpsertDataModelContext.UpsertDataModelContextBuilder;
import org.unidata.mdm.meta.dto.GetModelDTO;
import org.unidata.mdm.meta.dto.GetLookupDTO;
import org.unidata.mdm.rest.meta.converter.LookupEntityDefToLookupEntityDefinitionConverter;
import org.unidata.mdm.rest.meta.converter.LookupEntityDefinitionToLookupEntityDefConverter;
import org.unidata.mdm.rest.meta.converter.NamedDisplayableConverter;
import org.unidata.mdm.rest.meta.exception.MetaRestExceptionIds;
import org.unidata.mdm.rest.meta.ro.DeleteModelEntityRO;
import org.unidata.mdm.rest.meta.ro.GetModelRO;
import org.unidata.mdm.rest.meta.ro.LookupEntityRO;
import org.unidata.mdm.rest.meta.type.rendering.MetaModelInputRenderingAction;
import org.unidata.mdm.rest.meta.type.rendering.MetaModelOutputRenderingAction;
import org.unidata.mdm.rest.system.ro.ErrorResponse;
import org.unidata.mdm.rest.system.ro.RestResponse;
import org.unidata.mdm.system.exception.PlatformBusinessException;
import org.unidata.mdm.system.service.RenderingService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

/**
 * @author Michael Yashin. Created on 19.05.2015.
 */
@Path(LookupEntityRestService.SERVICE_PATH)
@Consumes({ "application/json" })
@Produces({ "application/json" })
public class LookupEntityRestService extends AbstractFilteringRestService {

    public static final String SERVICE_PATH = "lookup-entities";

    /**
     * Meta model service.
     */
    @Autowired
    private MetaModelService metaModelService;

    @Autowired
    private RenderingService renderingService;
    /**
     * Gets a list of lookup entities.
     *
     * @param pageRequest
     *            the page request
     * @return list of entity info
     */
    @GET
    @Operation(
        description = "Lookup list.",
        method = HttpMethod.GET,
        parameters = {
                @Parameter(name = "page", description = "Номер страницы, начинается с 1", in = ParameterIn.QUERY),
                @Parameter(name = "size", description = "Размер страницы", in = ParameterIn.QUERY),
                @Parameter(name = "sort", description = "Параметры сортировки, URL-encoded JSON", in = ParameterIn.QUERY),
                @Parameter(name = "draftId", description = "Exisitng draft ID. Optional.", in = ParameterIn.QUERY)
        },
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
        }
    )
    public Response findAll(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") long draftId) {

        GetModelDTO result = metaModelService.get(GetDataModelContext.builder()
            .allLookups(true)
            .draftId(draftId > 0 ? draftId : null)
            .build());

        return ok(NamedDisplayableConverter.to(result.getLookups().stream()
                .map(GetLookupDTO::getLookup)
                .filter(el -> allow(el.getName()))
                .collect(Collectors.toList())));
    }

    /**
     * Gets a lookup entity by id (name).
     *
     * @param id
     *            the id (name)
     * @return lookup entity
     */
    @GET
    @Path("{id}")
    @Operation(
        description = "Gets a lookup by ID.",
        method = HttpMethod.GET,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
        }
    )
    public Response getById(
            @Parameter(description = "ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft ID. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") long draftId,
            @Parameter(description = "Check for data existance.", in = ParameterIn.QUERY) @QueryParam("checkData") @DefaultValue("true") boolean checkData) {

        if (!allow(id)) {
            return notAuthorized(id);
        }

        GetModelRO ro = new GetModelRO();
        ro.setCheckData(checkData);
        ro.setDraft(draftId > 0);
        ro.setDraftId(draftId);
        ro.setId(id);

        GetDataModelContextBuilder context = GetDataModelContext.builder()
                .lookupIds(Collections.singletonList(ro.getId()))
                .draftId(draftId > 0 ? draftId : null);

        renderingService.renderInput(MetaModelInputRenderingAction.GET_LOOKUP_MODEL_INPUT, context, ro);

        GetModelDTO model = metaModelService.get(context.build());

        throwIfNotFound(model);

        LookupEntityRO response = LookupEntityDefToLookupEntityDefinitionConverter.convert(model.getLookups().get(0).getLookup());

        renderingService.renderOutput(MetaModelOutputRenderingAction.GET_LOOKUP_MODEL_OUTPUT, model, response);

        return ok(new RestResponse<>(response));
    }

    /**
     * Creates a lookup entity.
     *
     * @param lookupEntity
     *            entity to create
     * @return created entity
     */
    @POST
    @Operation(
        description = "Create a lookup.",
        method = HttpMethod.POST,
        parameters = @Parameter(name = "draftId", description = "Exisitng draft ID. Optional.", in = ParameterIn.QUERY),
        requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = LookupEntityRO.class)), description = "Request body"),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
        }
    )
    public Response create(LookupEntityRO lookupEntity, @QueryParam("draftId") @DefaultValue("0") long draftId) {

        if (!allow(lookupEntity.getName())) {
            return notAuthorized(lookupEntity.getName());
        }

        UpsertDataModelContextBuilder uCtx = LookupEntityDefinitionToLookupEntityDefConverter.convert(lookupEntity, draftId);

        renderingService.renderInput(MetaModelInputRenderingAction.UPSERT_LOOKUP_MODEL_INPUT, uCtx, lookupEntity);

        metaModelService.upsert(uCtx.build());

        GetModelRO ro = new GetModelRO();
        ro.setId(lookupEntity.getName());
        ro.setDraft(draftId > 0);
        ro.setDraftId(draftId);
        ro.setCheckData(true);

        GetDataModelContextBuilder gCtx = GetDataModelContext.builder()
                .lookupIds(Collections.singletonList(lookupEntity.getName()))
                .draftId(draftId > 0 ? draftId : null);

        renderingService.renderInput(MetaModelInputRenderingAction.GET_LOOKUP_MODEL_INPUT, gCtx, ro);

        GetModelDTO result = metaModelService.get(gCtx.build());

        throwIfNotFound(result);

        LookupEntityRO response = LookupEntityDefToLookupEntityDefinitionConverter.convert(
                result.getLookups().get(0).getLookup()
        );

        renderingService.renderOutput(MetaModelOutputRenderingAction.GET_LOOKUP_MODEL_OUTPUT, result, response);

        return ok(new RestResponse<>(response));

    }

    /**
     * Updates lookup entity.
     *
     * @param lookupEntity
     *            the entity
     * @return updated entity
     */
    @PUT
    @Path("{id}")
    @Operation(
        description = "Update a lookup.",
        method = HttpMethod.PUT,
        parameters = {
                @Parameter(name = "id", description = "Lookup id", in = ParameterIn.PATH),
                @Parameter(name = "draftId", description = "Existing draft ID. Optional.", in = ParameterIn.QUERY, required = false)
        },
        requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = LookupEntityRO.class)), description = "Запрос на вставку"),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
        }
    )
    public Response update(
            LookupEntityRO lookupEntity,
            @PathParam("id") String possiblyOldName,
            @QueryParam("draftId") @DefaultValue("0") long draftId) {

        if (!allow(lookupEntity.getName())) {
            return notAuthorized(lookupEntity.getName());
        }

        UpsertDataModelContextBuilder uCtx = LookupEntityDefinitionToLookupEntityDefConverter.convert(lookupEntity, draftId);

        renderingService.renderInput(MetaModelInputRenderingAction.UPSERT_LOOKUP_MODEL_INPUT, uCtx, lookupEntity);

        metaModelService.upsert(uCtx.build());

        GetModelRO ro = new GetModelRO();
        ro.setId(lookupEntity.getName());
        ro.setDraftId(draftId);
        ro.setCheckData(true);

        GetDataModelContextBuilder gCtx = GetDataModelContext.builder()
                .lookupIds(Collections.singletonList(lookupEntity.getName()))
                .draftId(draftId > 0 ? draftId : null);

        renderingService.renderInput(MetaModelInputRenderingAction.GET_LOOKUP_MODEL_INPUT, gCtx, ro);

        GetModelDTO model = metaModelService.get(gCtx.build());

        if (model.getLookups().isEmpty()) {
            throw new PlatformBusinessException("Entity not found", MetaRestExceptionIds.EX_META_DATA_LOOKUP_NOT_FOUND);
        }

        LookupEntityRO response = LookupEntityDefToLookupEntityDefinitionConverter.convert(model.getLookups().get(0).getLookup());

        renderingService.renderOutput(MetaModelOutputRenderingAction.GET_LOOKUP_MODEL_OUTPUT, model, response);

        return ok(new RestResponse<>(response));
    }

    /**
     * Delete lookup entity.
     *
     * @param id
     *            id to delete
     * @return 200 Ok
     */
    @DELETE
    @Path("{id}")
    @Operation(
        description = "Removes a lookup.",
        method = HttpMethod.DELETE,
        parameters = {
                @Parameter(description = "Lookup id.", in = ParameterIn.PATH, name = "id"),
                @Parameter(description = "Existing draft id. Optional.", in = ParameterIn.QUERY, name = "draftId")
        },
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = String.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
        }
    )
    public Response delete(@PathParam("id") String id, @QueryParam("draftId") @DefaultValue("0") long draftId) {

        if (!allow(id)) {
            return notAuthorized(id);
        }

        UpsertDataModelContextBuilder deleteRequestContextBuilder = UpsertDataModelContext.builder()
                .lookupEntitiesDelete(id)
                .draftId(draftId > 0 ? draftId : null);

        renderingService.renderInput(
                MetaModelInputRenderingAction.DELETE_LOOKUP_META_MODEL_INPUT,
                deleteRequestContextBuilder,
                new DeleteModelEntityRO(id)
        );

        metaModelService.upsert(deleteRequestContextBuilder.build());

        return ok("");
    }
    /**
     * Get all available tags for this entity.
     * @param id entity name
     * @param draft is draft
     * @return all available tags for thos entity.
     */
    @GET
    @Path(value = "/tags/{id}")
    @Operation(
            description = "Получить список тэгов для справочника.",
            method = HttpMethod.GET,
            responses = {
                @ApiResponse(content = @Content(schema = @Schema(implementation = RestResponse.class)), responseCode = "200"),
                @ApiResponse(content = @Content(schema = @Schema(implementation = ErrorResponse.class)), responseCode = "500")
            }
        )

    public Response getAllTags(
            @Parameter(description = "Идентификатор,", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Черновик?,", in = ParameterIn.QUERY) @QueryParam("draft") @DefaultValue("false") boolean draft) {

//        LookupEntity lookupEntity;
//        if (draft) {
//            lookupEntity = metaDraftService.getLookupEntityById(id);
//        } else {
//            lookupEntity = metaModelService.getLookupEntityById(id);
//        }
//        if (Objects.isNull(lookupEntity)) {
//            throw new PlatformBusinessException("Lookup entity not found", MetaRestExceptionIds.EX_META_LOOKUP_ENTITY_NOT_FOUND);
//        }
        Set<String> tags = new HashSet<>();

        return ok(new RestResponse<>(tags));
    }

    private void throwIfNotFound(GetModelDTO model) {
        if (model.getLookups().isEmpty()) {
            throw new PlatformBusinessException("Lookup not found", MetaRestExceptionIds.EX_META_DATA_LOOKUP_NOT_FOUND);
        }
    }
}